﻿/*
 * Copyright © 2011 Tristan Reeves, treeves@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
using System;
using System.Reflection;
using Sprinkles.Secondary;

namespace Sprinkles.Attributes
{
    public static class DeploymentHostExtensions
    {
        public static void RegisterTypeFromAttributes(
            this DeploymentHost host,
            Type objectType,
            bool isSingleton,
            bool isLazyInit,
            Scope scope,
            string registrationName,
            Type[] defaultForTypes,
            PropertyImportInfo[] importInfos,
            ConstructorInfo constructorInfo,
            ConstructorArgumentImportInfo[] constructorImportInfos
            )
        {
            defaultForTypes = defaultForTypes ?? new Type[0];
            registrationName = (registrationName ?? "").Trim();
            object deployment;

            //create the basic deployment object
            if (registrationName == "")
            {
                var mi = typeof(DeploymentHost).GetMethod("Register", Type.EmptyTypes);
                mi = mi.MakeGenericMethod(objectType);
                deployment = mi.Invoke(host, null);
            }
            else
            {
                var mi = typeof(DeploymentHost).GetMethod("Register", new[] { typeof(string) });
                mi = mi.MakeGenericMethod(objectType);
                deployment = mi.Invoke(host, new object[] { registrationName });
            }

            //singleton, lazy, scope
            deployment = deployment
                .GetType()
                .GetMethod("Singleton")
                .Invoke(deployment, new object[] { isSingleton });
            if (isSingleton)
            {
                deployment = deployment
                    .GetType()
                    .GetMethod("LazyInit")
                    .Invoke(deployment, new object[] { isLazyInit });
            }
            if (scope != Scope.None)
            {
                deployment = deployment
                    .GetType()
                    .GetMethod("SetScope")
                    .Invoke(deployment, new object[] { scope });
            }

            //mark as default for specified types
            foreach (var type in defaultForTypes)
            {
                var mi = deployment.GetType().GetMethod("IsDefaultFor", new[] { typeof(Type) });
                deployment = mi.Invoke(deployment, new object[] { type });
            }

            for (int i = 0; i < importInfos.Length; i++)
            {
                var propertyInfo = importInfos[i].ReflectionInfo;
                var resolutionType = importInfos[i].ImportResolutionType;
                var data = importInfos[i].Data;

                var mi = deployment.GetType().GetMethod("Property", new[] { typeof(string) });
                mi = mi.MakeGenericMethod(propertyInfo.PropertyType);

                var propertySettingObject = mi.Invoke(deployment, new[] { propertyInfo.Name }); ;


                if (resolutionType == ImportResolutionType.Auto)
                {
                    deployment = propertySettingObject
                        .GetType()
                        .GetMethod("SetAuto", Type.EmptyTypes)
                        .Invoke(propertySettingObject, null);
                }
                else if (resolutionType == ImportResolutionType.Ref)
                {
                    var val = (data ?? "").ToString().Trim();

                    deployment = propertySettingObject
                        .GetType()
                        .GetMethod("SetReference", new[] { typeof(string) })
                        .Invoke(propertySettingObject, new[] { val });
                }
                else if (resolutionType == ImportResolutionType.SpEL)
                {
                    var val = (data ?? "").ToString().Trim();

                    deployment = propertySettingObject
                        .GetType()
                        .GetMethod("SetExpression", new[] { typeof(string) })
                        .Invoke(propertySettingObject, new[] { val });
                }
                else if (resolutionType == ImportResolutionType.Literal)
                {
                    var val = data;

                    deployment = propertySettingObject
                        .GetType()
                        .GetMethod("SetUntypedObject", new[] { typeof(object) })
                        .Invoke(propertySettingObject, new[] { val });
                }
                else throw new Exception("Unkown resolutionType [" + resolutionType + "]");
            }


            if (constructorInfo != null)
            {
                var internalDeployment = deployment as IInternalDeployment;
                if (internalDeployment == null) throw new NullReferenceException("internalDeployment");

                foreach (var info in constructorImportInfos)
                {
                    object val = null;
                    switch (info.ImportResolutionType)
                    {
                        case ImportResolutionType.Auto:
                            val = new AutoReference();
                            break;
                        case ImportResolutionType.Literal:
                            val = info.Data;
                            break;
                        case ImportResolutionType.Ref:
                            val = new SprinklesRuntimeObjectReference(info.Data.ToString());
                            break;
                        case ImportResolutionType.SpEL:
                            val = new SprinklesExpressionHolder(info.Data.ToString());
                            break;
                        default:
                            throw new ArgumentOutOfRangeException("unknown ImportResolutionType [" + info.ImportResolutionType + "]");
                    }

                    var arg = new Arg()
                    {
                        DeclaredType = info.ReflectionInfo.ParameterType,
                        Index = info.ReflectionInfo.Position,
                        Name = info.ReflectionInfo.Name,
                        Value = val
                    };
                    internalDeployment.AddConstructorArg(arg);
                }
            }


            var doneMethod = deployment.GetType().GetMethod("Done");
            doneMethod.Invoke(deployment, null);
        }
    }
}
